2

1.简介

哈希表(hash table)又被称为散列表,可能是翻译的问题好多书上一会儿称散列一会儿称哈希,更有甚者煞有介事的对此进行区分。经过简单的搜索(wiki链接)发现这两个词是一回事。由此可见学好英语是多么重要。(我是一名渴望学好英语的英文渣。。)

1.1定义

这里引用一下维基百科的定义:

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数(Hash function),将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

这段话里加粗的地方暂时存疑,因为和下一句话说法有冲突,感觉改为根据哈希函数与键计算出来的值,即哈希值访问内存存储位置的数据结构会好点(当然也有可能是我阅读理解不好,哈哈)

1.2一些点

哈希表是

  • 一种动态(指数据存入后,还会进行增删查改等工作)集合结构
  • 至少需要支持insert,search,delete等操作
  • 普通数组的推广概念

    • 数组是直接寻址
    • 当实际存储的key数量小于key的总数量,使用哈希表是直接数组寻址的有效替代
  • 不是直接把key作为数组下标,而是根据哈希函数与key计算出相应的下标

2直接寻址表(direct-address table)

2.1废话

资料找多了会发现一个很严重的问题,它们之间可能会有冲突,从简介中介绍的名字问题就可以看出。同样,有些书把直接寻址表也视作一种哈希表,不过哈希表既然是数组的一种推广,那也就不要在这些细枝末节上计较了。

2.2介绍

当关键词的数量比较小时,这种方法是一种简单有效的方法,在我的文章《如何随机&&去重返回新数组》中3.1节给出的方法就用到了直接寻址处理问题,代码中数组indexArr就是。这是一种空间换时间的做法,定义一个大于等于key数量的数组,value部分全部初始化为null,然后进行数据的存取。这也是我们经常使用的。

3哈希表(hash table)

直接寻址的缺点在于如果数据量很大,占用空间就很大,因为首先你得初始化一个巨大的数组,无论数据是否存入。如果用一句话总结哈希表就是:hash浓缩为一句话:将元素通过一个函数转换为整数,使得该整数可以尽量唯一的表达这个元素

3.1js中数组和对象与哈希表的关系

但是在js中其实这个问题有待于商榷,因为js的数组还有对象都可以存任意键值而且无需提前定义长度,还可以随意增删。所以有的文章就指出其实js的数组和对象就的底层实现就是哈希表(文章地址),虽然文章中只是提到对象,但是基于js存在key-value形式的数组,我猜原理应该很是类似。

3.2基础的哈希函数

设哈希函数为H,数据的键设为key,转换后的值为整数H(key)。常见的有平方取中发,除留余数法,线性变化法(H(key)=a*key+b)。这里着重介绍除留余数法。

3.2.1除留余数法

公式:
$$ H(key)=key\%mod $$

把key除以一个数mod得到的余数作为hash值的方法,通过这个哈希函数可以把很大的数转换为不超过mod的整数,这表示数组的长度必须不小于mod(js中无所谓),当mod是一个素数时H(key)能尽可能的覆盖[0,mod)范围内的每一个数。

3.3冲突

看3.2.1的公式就可以知道,必然会出现两个不同的key1,key2他们的hash的值H(key1)=H(key2)。如果直接把hash值作为数组下表标则会出现覆盖的情况,我们称之为冲突,由此看出hash函数不是单射,这样也就表明hash值是不可逆的。

3.3.1常见的解决冲突的方法

  1. 线性探查法
  2. 平方探查法
  3. 链地址法

1,2都要重新计算hash值,3不需要,而且3是C语言里常见的解决方法,思想是把所有H(key)相同的key连成一条单链表(当然用一个数组也是可以的),然后查找时遍历单链表寻找数据。这些都是底层,大部分语言都封装有库。

3.4字符串hash初步

字符串hash是指将一个字符串S映射为一个整数,使得该整数可以尽可能唯一地代表字符串S。为什么要这么做呢,因为好多语言的数组的下标只能接受整数,例如C语言这种静态的语言和js这种动态语言数据存储上差异很大。js中使用对象存储key-value形式的数据增删查改都非常方便,但是在C语言中需要很多数据结构配合的使用才能实现。

  function hashFunc(s='hello word'){
        let id=0,len,arr=[];
        len=s.length;
        arr=s.split("");
        for(let i=0;i<len;i++){
            id += arr[i].charCodeAt();//str.charCodeAt(index)用于获取字符的ascii码
        }
        return id%57;//找一个素数用来限制数组大小
    }

js实现这个函数还是很方便的,就是字符ascii码相加即可。当然还可以使用更复杂的方式来避免冲突。

3.5js实现哈希表

我们将要实现hashTable这个类分别实现插入、查找、删除、打印等方法。

    class HashTable {
        constructor() {
            this.table = {'3212':{'ddd':'ddd','ee':'2312'}};//测试递归是否正常
        }

        _hashFunc(key) {
            let id = 0, len, arr = [];
            len = key.length;
            arr = key.split("");
            for (let i = 0; i < len; i++) {
                id += arr[i].charCodeAt();//str.charCodeAt(index)用于获取字符的ascii码
            }
            return id%57;//找一个素数用来限制数组大小
        }
        insert(key,value){
            if(typeof key !='object'){//可以只接受一个对象
                let id = this._hashFunc(key);
                if(!this.table[id]){
                    this.table[id]={};
                }
                if(!this.table[id][key]){
                    this.table[id][key]=value;
                }
            }else{
                for(let i in key){
                    this.insert(i,key[i])
                }
            }

        }
        search(key){
            let id = this._hashFunc(key);
            if(!this.table[id] || !this.table[id][key]) return null;
            return this.table[id][key];
        }
        delete(key){
            let id = this._hashFunc(key);
            if(this.table[id])
                if(this.table[id][key])
                   return delete this.table[id][key]
        }
        print(table=this.table){//递归输出hashtable的值
            if(typeof table=='object'){
                for(let key in table){
                    this.print(table[key])
                    if(typeof table[key]!='object')
                    console.log(key,'+',table[key])
                }
            }

        }
    }
    let hash = new HashTable()
    hash.insert({'abc':'ddd@qq.com','bac':'33@qq.com','ddic':'2343@gmail.com'});
    hash.print();
    console.warn('delete abc')
    hash.delete('abc');
    hash.print();
    console.log(hash.search('bac'))

这个代码里用了对象嵌套对象的形式实现了链地址法处理冲突在C语言中会选择数组+链表的形式实现,当后面写到链表的时候会重新改一下。其实就像上文中所述,js中的对象可能就是封装一个哈希表,而且key值是唯一的,连哈希函数貌似都可以省了了。

参考书目

《算法笔记》
《算法导论》
《学习JavaScript数据结构与算法》
《ECMAScript 6 入门》


梦中的贝壳
41 声望6 粉丝